(ns scripts.cljmon
  (:use scripts.cljmon.monitor
        scripts.cljmon.url
        scripts.cljmon.logger
        scripts.cljmon.parser
        scripts.cljmon.utils
        scripts.cljmon.chart
        scripts.cljmon.excel
        scripts.cljmon.agent.server
        scripts.cljmon.config)
  (:use clojure.tools.logging)
  (:use util.cmd
        util.utils))

;;; main

(def default-unit "MB")

(defn- start
  ([args]
    (with-command-line args
      {:description "System monitor utility in Clojure, part of M3O Testing Toolkit."
       :defaults {:rc "cljmonrc"}}
      [[system? "Monitor system CPU and memories" true]
       [disk? "Monitor disk IO" false]
       [wait-cpu? "Monitor wait CPU (Linux only)" false]
       [disk-space? "Monitor disk space" false]
       [network? "Monitor Network Traffic" false]
       [pid* p* "Monitor a process CPU and memories by pid"]
       [pids* "Monitor all processes with a certain name"]
       [port* "Monitor a process by a TCP port"]
       [me? "Monitor the cljmon process" false]
       [url* "Monitor remote resources at address"]
       [interval i "Recording interval by seconds" 3]
       [log l "Log path to save. Set to - will use stdout (no log file)" default-log]
       [format "Log in csv or json" "csv"]
       [backup? "Automatcially rename existing logs" false]
       [rollover? "Automatically rename old logs at 00:00" false]
       [print? "Print data to screen"]
       [memory-unit "Unit for memory" default-unit]
       [disk-unit "Unit for disk io" default-unit]
       [network-unit "Unit for network io" default-unit]
       [read-cpu? r? "Analysis the CPU usage in cljmon log file" false]
       [read-memory? rm? "Analysis the Memory usage in cljmon log file" false]
       [read-disk? rd? "Analysis the Disk usage in cljmon log file" false]
       [read-network? rn? "Analysis the network usage in cljmon log file" false]
       [range "Select range for analysis. auto|all|today|yesterday|date" "auto"]
       [range-from "Start of the range. yesterday|date|datetime" nil]
       [range-to "End of the range. date|datetime" nil]
       [chart? "Generate charts for cljmon log" false]
       [charts? "Same as --chart" false]
       [excel? "Convert cljmon.log to cljmon.xls" false]
       [create-dir "Create directory for charts" "."]
       [ps? "List process information" false]
       [pdh? "Use PDH API to read process memory usage (Windows only)" false]
       [winapi? "Use Windows API via JNA to read process memory usage (Windows only)" false]
       [log-process-arguments? "Log process arguments to cljmon.log" false]
       [additions "Not available for command line usage" nil]
       [agent? "Start as agent"]
       [agent-port "Agent port" DEFAULT_AGENT_PORT]
       remaining]
      ; TODO: remove bindings
      (binding [*logfile* (if (not= log "-") log nil)
                *format* (case format "csv" :csv "json" :json)
                *backup* backup?
                *rollover* rollover?
                *range* (compute-range range range-from range-to)
                *print-screen* (or print? (= log "-"))]
        (cond
          agent? (start-agent-server (Integer/parseInt (str agent-port)))
          read-cpu? (display-cpu (read-cpu log))
          read-memory? (display-memory (read-memory log))
          read-disk? (display-disk-io (read-disk-io log))
          read-network? (display-network-io (read-network-io log))
          (or chart? charts?) (do (println "Generating charts...") (println "Please check" (generate-html (or (seq remaining) log) create-dir)))
          excel? (convert-cljmon-log-to-excel log)
          :else (let [pollrate (parse-seconds interval)
                      queries (remove nil? (flatten [:timestamp
                                                     (when system?
                                                       {:type :system-info})
                                                     (for [process-name pid*]
                                                       {:type :process-info :by :name :name process-name :log-process-arguments log-process-arguments?})
                                                     (for [process-name pids*]
                                                       {:type :process-info :by :name :name process-name :multiple-instances true :log-process-arguments log-process-arguments?})
                                                     (for [port port*]
                                                       {:type :process-info :by :port :port (Integer/parseInt port) :log-process-arguments log-process-arguments?})
                                                     (when me?
                                                       {:type :process-info :by :me :log-process-arguments log-process-arguments?})
                                                     (when wait-cpu?
                                                       {:type :wait-cpu})
                                                     (when network?
                                                       {:type :network-io})
                                                     (when disk?
                                                       {:type :disk-io})
                                                     (when disk-space?
                                                       {:type :disk-space})
                                                     (for [url url*]
                                                       (translate-resource-url url {:memory-unit memory-unit
                                                                                    :disk-unit disk-unit
                                                                                    :network-unit network-unit
                                                                                    :log-process-arguments log-process-arguments?}))
                                                     (for [[k f] additions]
                                                       {:type :fn :label k :fn f})]))
                      q {:pollrate pollrate
                         :queries queries
                         :pdh? pdh?
                         :winapi? winapi?
                         :memory-unit memory-unit
                         :network-unit network-unit
                         :disk-unit disk-unit}]
                  (if ps?
                    (ps q)
                    (start-monitor q)))))))
  ([]
    (start *command-line-args*)))

(defn- start-thread [f]
  (let [fun #(try
               (f)
               (catch Exception e
                 (error e e)
                 (throw e)))
        thread (Thread. fun "Cljmon thread")]
    (.start thread)
    thread))

(defn stop-thread [thread]
  (while (.isAlive thread)
    (.interrupt thread)
    (.join thread 3000)))

(defn- check-thread-alive [thread]
  (Thread/sleep 1000)
  (if (.isAlive thread)
    thread
    (throw (RuntimeException. (format "Cljmon thread %s is not alive!" thread)))))

(defn start-cmd-thread-checked [args]
  (check-thread-alive (start-thread #(start args))))

(defn start-monitor-thread-checked [opts]
  (check-thread-alive (start-thread #(start-monitor opts))))

(defn- stop-thread-when-exit [thread]
  (exit-do (when (.isAlive thread) (.interrupt thread))))

(defn -main [& args]
  (stop-thread-when-exit (Thread/currentThread))
  (start))

